using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Xml;
using Nemerle.Collections;

using Nextem.String;

namespace IronMacro.Core {
	public type MapRect = int * int * int * int;
	
	public enum TriggerOn {
		| Appear
		| Disappear
		| Change
		| Both
	}
	
	public variant Spec {
		| Class {
				_Name : string;
				mutable _Size : int * int;
				Members : Hashtable [string, MapRect * Spec]
			}
		| Trigger {
				_Name : string;
				On : TriggerOn;
				mutable Image : string;
			}
		| EnumMapping {
				_Name : string;
				mutable _Size : int * int;
				Members : Hashtable [string, int * string]
			}
		| StringMapping {
				_Name : string;
				Mapping : Hashtable [string, string]
			}
		| IntMapping {
				_Name : string;
				Mapping : Hashtable [string, string]
			}
		| BoolMapping {
				_Name : string;
				mutable True : string;
				mutable False : string
			}
		| Ref {
				_Name : string;
				_Spec : Spec
			}
		
		public Name : string {
			get {
				match(this) {
					| Class(name, _, _)
					| Trigger(name, _, _)
					| EnumMapping(name, _, _)
					| StringMapping(name, _)
					| IntMapping(name, _)
					| BoolMapping(name, _, _)
					| Ref(name, _) => 
						name
				}
			}
		}
		
		public Size : int * int {
			get {
				match(this) {
					| Class(_, size, _)
					| EnumMapping(_, size, _) => size
					| _ => (-1, -1)
				}
			}
			set {
				match(this) {
					| Class as x => x._Size = value
					| EnumMapping as x => x._Size = value
					| _ => ()
				}
			}
		}
		
		public override ToString() : string {
			mutable ret : string;
			match(this) {
				| EnumMapping(name, size, members) =>
					ret = "\t<Enum name=\"{0}\"{1}>\n" <- (
							name, 
							if(size[0] == -1 && size[1] == -1) ""
							else " size=\"{0},{1}\"" <- (size[0], size[1])
						);
					foreach(pair in members) {
						def (value, image) = pair.Value;
						ret += "\t\t<Elem name=\"{0}\" value=\"{1}\" {2}/>\n" <- (
								pair.Key, 
								value, 
								if(image == null) ""
								else "image=\"{0}\" " <- image
							)
					}
					ret + "\t</Enum>\n"
				| Class(name, size, members) =>
					ret = "\t<Class name=\"{0}\"{1}>\n" <- (
							name, 
							if(size[0] == -1 && size[1] == -1) ""
							else " size=\"{0},{1}\"" <- (size[0], size[1])
						);
					foreach((rect, spec) in members.Values) {
						def rect = 
							if(rect[0] == -1 && rect[1] == -1 && rect[2] == -1 && rect[3] == -1) ""
							else "rect=\"{0},{1},{2},{3}\" " <- (rect[0], rect[1], rect[2], rect[3]);
						match(spec) {
							| Trigger(name, on, image) =>
								ret += "\t\t<Trigger name=\"{0}\" on=\"{1}\" {2}{3}/>\n" <- (
										name, 
										match(on) {
											| Appear => "appear"
											| Disappear => "disappear"
											| Both => "both"
											| Change => "change"
										}, 
										if(image == null) ""
										else "image=\"{0}\" " <- image, 
										rect
									)
							| Ref(name, spec) =>
								ret += "\t\t<Prop name=\"{0}\" type=\"{1}\" {2}/>\n" <- (name, spec.Name, rect)
							| StringMapping(name, mapping)
							| IntMapping(name, mapping)
							| BoolMapping(name, t, f)
								with mapping = Hashtable([("true", t), ("false", f)]) => 
								mutable subret = "";
								foreach(pair in mapping)
									subret += 
										if(pair.Value == null) ""
										else "\t\t\t<Value value=\"{0}\" image=\"{1}\" />\n" <- (pair.Key, pair.Value);
								
								ret += "\t\t<Prop name=\"{0}\" type=\"{1}\" {2}>\n{3}\t\t</Prop>\n" <- (
										name, 
										match(spec) {
											| StringMapping => "string"
											| IntMapping => "int"
											| BoolMapping => "bool"
											| _ => ""
										}, 
										rect, 
										subret
									)
							| _ => ()
						}
					}
					ret + "\t</Class>\n"
				| _ => ""
			}
		}
		
		public static Load(fn : string) : list [Spec] {
			def hash = Hashtable.[string, Spec]();
			mutable order = [];
			
			def fs = File.OpenRead(fn);
			def xmlDoc = XmlDocument();
			xmlDoc.Load(fs);
			
			def enumNodes = xmlDoc.GetElementsByTagName("Enum");
			foreach(node is XmlNode in enumNodes) {
				def attrs = node.Attributes;
				def enumName = 
					match(attrs.GetNamedItem("name")) {
						| null => throw Exception("Enum requires a name attribute.")
						| x => x.InnerText
					}
				def size =
					match(attrs.GetNamedItem("size")) {
						| null => (-1, -1)
						| x =>
							def (w, h) = x.InnerText.Split1(",");
							(int.Parse(w), int.Parse(h))
					}
				
				order ::= enumName;
				mutable i = 0;
				def members = Hashtable();
				hash[enumName] = Spec.EnumMapping(enumName, size, members);
				
				foreach(child is XmlNode in node) {
					unless(child.Name == "Elem") throw Exception("Enum can only contain Elem elements.");
					
					def attrs = child.Attributes;
					def name = match(attrs.GetNamedItem("name")) { | null => null | x => x.InnerText }
					def value = match(attrs.GetNamedItem("value")) { | null => 0 | x => int.Parse(x.InnerText) }
					def image = match(attrs.GetNamedItem("image")) { | null => null | x => x.InnerText }
					
					when(name == null) throw Exception("Enum elements require a name.");
					
					members[name] = 
						(
							if(value == 0) {
								i++;
								i - 1
							} else {
								i = value+1;
								value
							}, 
							image
						)
				}
			}
			
			def classNodes = xmlDoc.GetElementsByTagName("Class");
			foreach(node is XmlNode in classNodes) {
				def attrs = node.Attributes;
				def className = 
					match(attrs.GetNamedItem("name")) {
						| null => throw Exception("Class requires a name attribute.")
						| x => x.InnerText
					}
				def size =
					match(attrs.GetNamedItem("size")) {
						| null => (-1, -1)
						| x =>
							def (w, h) = x.InnerText.Split1(",");
							(int.Parse(w), int.Parse(h))
					}
				
				order ::= className;
				def members = Hashtable();
				hash[className] = Spec.Class(className, size, members);
				
				foreach(child is XmlNode in node) {
					def attrs = child.Attributes;
					def name = match(attrs.GetNamedItem("name")) { | null => null | x => x.InnerText }
					def typ = match(attrs.GetNamedItem("type")) { | null => null | x => x.InnerText }
					def on = match(attrs.GetNamedItem("on")) { | null => null | x => x.InnerText }
					def image = match(attrs.GetNamedItem("image")) { | null => null | x => x.InnerText }
					def rect = match(attrs.GetNamedItem("rect")) { | null => null | x => x.InnerText }
					
					def rect = 
						if(rect == null) (-1, -1, -1, -1)
						else {
							def (a, b, c, d) = rect.Split3(",");
							(int.Parse(a), int.Parse(b), int.Parse(c), int.Parse(d))
						}
					
					when(name == null) throw Exception("Class members require a name.");
					
					def member = 
						match(child.Name) {
							| "Prop" =>
								def values = LoadValues(child);
								
								match(typ) {
									| "string" => Spec.StringMapping(name, values)
									| "int" => 
										Spec.IntMapping(name, values)
									| "bool" => 
										Spec.BoolMapping(
												name,
												if(values.ContainsKey("true")) values["true"] else null, 
												if(values.ContainsKey("false")) values["false"] else null
											)
									| rname => Spec.Ref(name, hash[rname])
								}
							
							| "Trigger" =>
								Spec.Trigger(
										name, 
										match(on) {
											| "appear" => TriggerOn.Appear
											| "disappear" => TriggerOn.Disappear
											| "both" => TriggerOn.Both
											| "change" => TriggerOn.Change
											| x => throw Exception("Unknown 'on' value to Trigger: {0}" <- x)
										}, 
										image
									)
							| x => throw Exception("Unknown member of Class: {0}" <- x)
						}
					members[name] = (rect, member)
				}
			}
			
			mutable ret = [];
			foreach(name in order)
				ret ::= hash[name];
			ret
		}
		
		static LoadValues(node : XmlNode) : Hashtable [string, string] {
			def values = Hashtable();
			
			foreach(child is XmlNode in node) {
				unless(child.Name == "Value") throw Exception("Class.Prop nodes can only include Value nodes.");
				
				def value = child.Attributes.GetNamedItem("value");
				def image = child.Attributes.GetNamedItem("image");
				unless(value != null && image != null) throw Exception("Class.Prop nodes must include value and image.");
				
				values[value.InnerText] = image.InnerText
			}
			
			values
		}
	}
}
